﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using COC.Debug;
using UnityEngine;
using COC.Tools.AI.Motion;

namespace COC.Character.AI.Internal.FSM
{
    /// <summary>
    /// it's a connector of Finite State Machine(a.k.a FSM)
    /// </summary>
    public class FSMConnector
    {
        protected internal IFSMStack StateStack;
        protected internal BaseBehaviour _baseBehaviour;
        Enum mStates;
        InterruptableCoroutine enterStateCoroutine;
        InterruptableCoroutine exitStateCoroutine;
        IEnumerator enterStateEnumerator;
        IEnumerator exitStateEnumerator;
        /// <summary>
        /// StateStack of executing coroutines
        /// </summary>
        Stack<IEnumerator> _mIEnumStack = new Stack<IEnumerator>();
        public FSMConnector(BaseBehaviour _baseBehaviour)
        {
            StateStack = new FSMStack();
            enterStateCoroutine = new InterruptableCoroutine(_baseBehaviour, _mIEnumStack);
            enterStateCoroutine = new InterruptableCoroutine(_baseBehaviour, _mIEnumStack);
            bindMoveBehavior(_baseBehaviour);
        }
        /// <summary>
        /// Gets the amount of time spent in the current state
        /// </summary>
        /// <value>
        /// The number of seconds in the current state
        /// </value>
        public float timeInCurrentState
        {
            get
            {
                return Time.time - _timeEnteredState;
            }
        }
        /// <summary>
        /// bind a movebehavior order to control steering of ai directly.
        /// </summary>
        /// <param name="aiBehaveAware"></param>
        public void bindMoveBehavior(BaseBehaviour aiBehave)
        {
            if (_baseBehaviour == null)
            {
                registMoveBehavior(aiBehave);
                connect();
            }
            else
            {
                UnityEngine.Debug.LogWarning("it has already registed one behavior:" + _baseBehaviour.GetType().FullName);
            }
        }


        private void registMoveBehavior(BaseBehaviour aiBehave)
        {
            _baseBehaviour = aiBehave;
        }
        public void unbindMoveBehavior()
        {
            if (_baseBehaviour != null)
            {
                _baseBehaviour = null;///if a character do not use,then recycle it to put into pool[this will be supported at future] 
                disconnect();
            }
        }


        AiAction state;
        public AiAction currentState
        {
            get
            {
                return state;
            }
            set
            {
                if (value.Equals(state))
                    return;
                changeCurrentState(value);
            }

        }
        public void changeCurrentState(AiAction vAction)
        {
            state = vAction;
            pushSingleState(state);
        }
     
        public void pushSingleState(AiAction state)
        {
            StateStack.pushSingleState(state);
        }
        public AiAction popState()
        {
            return StateStack.popState();
        }
        public AiAction getCurrState()
        {
            return StateStack.getCurrState();
        }
        public int cleanStates()
        {
            return StateStack.cleanStates();
        }
        /// <summary>
        /// connect with AIBehaveAware class
        /// </summary>
        public void connect()
        {
            _baseBehaviour.onAttach(this);
        }
        /// <summary>
        /// disconnect AIBehaveAware class
        /// </summary>
        public void disconnect()
        {
            _baseBehaviour.onDettach();
        }
        public void executeState(int which)
        {
            AiAction state = getCurrState();
            DebugTool.Log(state.state);

        }
        Dictionary<object, Dictionary<string, Delegate>> _cache = new Dictionary<object, Dictionary<string, Delegate>>();
        private readonly static String PREFIX_ENTER_METHOD = "Enter";
        private readonly static String PREFIX_EXIT_METHOD = "Exit";
        private float _timeEnteredState;

        private T delegateMethod<T>(string methodRoot, T Default) where T : class
        {
            Dictionary<string, Delegate> lookup;
            if (!_cache.TryGetValue(state, out lookup))
            {
                _cache[state] = lookup = new Dictionary<string, Delegate>();

            }
            Delegate returnValue;
            if (!lookup.TryGetValue(methodRoot, out returnValue))
            {
                var mtd = ((AIBehave)_baseBehaviour).GetType().GetMethod(state.state.ToString() + "_" + methodRoot, System.Reflection.BindingFlags.Instance
                | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.InvokeMethod);

                if (mtd != null)
                {
                    UnityEngine.Debug.Log("mtd:"+mtd);
                    if (typeof(T) == typeof(Func<IEnumerator>) && mtd.ReturnType != typeof(IEnumerator))
                    {
                        Action a = Delegate.CreateDelegate(typeof(Action), _baseBehaviour, mtd) as Action;
                        Func<IEnumerator> func = () => { a(); return null; };
                        returnValue = func;
                    }
                    else
                        returnValue = Delegate.CreateDelegate(typeof(T), _baseBehaviour, mtd);
                }
                else
                {
                    returnValue = Default as Delegate;
                }
                lookup[methodRoot] = returnValue;
            }
            return returnValue as T;
        }
        /// <summary>
        /// A coroutine executor that can be interrupted
        /// </summary>
        public class InterruptableCoroutine
        {
            IEnumerator enumerator;
            MonoBehaviour _behaviour;
            private Stack<IEnumerator> _stack;

            /// <summary>
            /// Coroutine info for running YieldInstructions as a separate coroutine
            /// </summary>
            private class CoroutineInfo
            {
                /// <summary>
                /// The instruction to execute
                /// </summary>
                public YieldInstruction instruction;
                /// <summary>
                /// Whether the coroutine is complete
                /// </summary>
                public bool done;
            }

            /// <summary>
            /// A coroutine that runs a single yield instruction
            /// </summary>
            /// <returns>
            /// The instruction coroutine.
            /// </returns>
            /// <param name='info'>
            /// The info packet for the coroutine to run
            /// </param>
            IEnumerator YieldInstructionCoroutine(CoroutineInfo info)
            {
                info.done = false;
                yield return info.instruction;
                info.done = true;
            }

            /// <summary>
            /// Waits for a yield instruction
            /// </summary>
            /// <returns>
            /// The coroutine to execute
            /// </returns>
            /// <param name='instruction'>
            /// The instruction to run
            /// </param>
            IEnumerator WaitForCoroutine(YieldInstruction instruction)
            {
                var ci = new CoroutineInfo { instruction = instruction, done = false };
                _behaviour.StartCoroutine(YieldInstructionCoroutine(ci));
                while (!ci.done)
                    yield return null;
            }

            IEnumerator Run()
            {
                //Loop forever
                while (true)
                {
                    //Check if we have a current coroutine
                    if (enumerator != null)
                    {
                        //Make a copy of the enumerator in case it changes
                        var enm = enumerator;
                        //Execute the next step of the coroutine
                        var valid = enumerator.MoveNext();
                        //See if the enumerator has changed
                        if (enm == enumerator)
                        {
                            //If this is the same enumerator
                            if (enumerator != null && valid)
                            {
                                //Get the result of the yield
                                var result = enumerator.Current;
                                //Check if it is a coroutine
                                if (result is IEnumerator)
                                {
                                    //Push the current coroutine and execute the new one
                                    _stack.Push(enumerator);
                                    enumerator = result as IEnumerator;
                                    yield return null;
                                }
                                //Check if it is a yield instruction
                                else if (result is YieldInstruction)
                                {
                                    //To be able to interrupt yield instructions
                                    //we need to run them as a separate coroutine
                                    //and wait for them
                                    _stack.Push(enumerator);
                                    //Create the coroutine to wait for the yieldinstruction
                                    enumerator = WaitForCoroutine(result as YieldInstruction);
                                    yield return null;
                                }
                                else
                                {
                                    //Otherwise return the value
                                    yield return enumerator.Current;
                                }
                            }
                            else
                            {
                                //If the enumerator was set to null then we
                                //need to mark this as invalid
                                valid = false;
                                yield return null;
                            }
                            //Check if we are in a valid state
                            if (!valid)
                            {
                                //If not then see if there are any stacked coroutines
                                if (_stack.Count >= 1)
                                {
                                    //Get the stacked coroutine back
                                    enumerator = _stack.Pop();
                                }
                                else
                                {
                                    //Ensure we don't use this enumerator again
                                    enumerator = null;
                                }
                            }
                        }
                        else
                        {
                            //If the enumerator changed then just yield
                            yield return null;
                        }
                    }
                    else
                    {
                        //If the enumerator was null then just yield
                        yield return null;
                    }
                }
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="StateMachineBehaviour.InterruptableCoroutine"/> class.
            /// </summary>
            /// <param name='behaviour'>
            ///     The behaviour on which the coroutines should run
            /// </param>
            /// <param name="stack"></param>
            public InterruptableCoroutine(MonoBehaviour behaviour, Stack<IEnumerator> stack)
            {
                _behaviour = behaviour;
                _stack = stack;
                _behaviour.StartCoroutine(Run());
            }

          
            /// <summary>
            /// Call the specified coroutine
            /// </summary>
            /// <param name='enm'>
            /// The coroutine to call
            /// </param>
            public void Call(IEnumerator enm)
            {
                _stack.Push(enumerator);
                enumerator = enm;
            }

            /// <summary>
            /// Run the specified coroutine with an optional stack
            /// </summary>
            /// <param name='enm'>
            /// The coroutine to run
            /// </param>
            /// <param name='stack'>
            /// The stack that should be used for this coroutine
            /// </param>
            public void Run(IEnumerator enm, Stack<IEnumerator> stack = null)
            {
                enumerator = enm;
                if (stack != null)
                {
                    _stack = stack;
                }
                else
                {
                    _stack.Clear();
                }

            }

            /// <summary>
            /// Creates a new stack for executing coroutines
            /// </summary>
            /// <returns>
            /// The stack.
            /// </returns>
            public Stack<IEnumerator> CreateStack()
            {
                var current = _stack;
                _stack = new Stack<IEnumerator>();
                return current;
            }

            /// <summary>
            /// Cancel the current coroutine
            /// </summary>
            public void Cancel()
            {
                enumerator = null;
                _stack.Clear();
            }


        }
        private void getStateMethods()
        {
            state.enterState= delegateMethod<Func<IEnumerator>>(PREFIX_ENTER_METHOD, DoNothing);
            UnityEngine.Debug.Log("state.enterState:" + state.enterState);
            state.exitState = delegateMethod<Func<IEnumerator>>(PREFIX_EXIT_METHOD, DoNothing);
        }
        public static IEnumerator DoNothing()
        {
            return null;
        }
        public void Call(object s)
        {
            state = new AiAction
            {
                state = s,
                time=timeInCurrentState

            };

            ConfigureCurrentStateForCall();
        }
        //Configures the state machine when the new state has been called
        void ConfigureCurrentStateForCall()
        {
            getStateMethods();
            enterStateEnumerator = state.enterState();
            enterStateCoroutine.Run(enterStateEnumerator);
        }
        /// <summary>
        /// Return this state from a call
        /// </summary>
        public void Return()
        {
           
            if (state.exitState != null)
            {
                exitStateEnumerator = state.exitState();
                exitStateCoroutine.Run(exitStateEnumerator);
            }
            if (StateStack.getStack().Count > 0)
            {
                state = StateStack.getStack().Pop();
                enterStateCoroutine.Run(state.enterState());
            }
            _timeEnteredState = Time.time - state.time;
        }
        /// <summary>
        /// Return the state from a call with a specified state to 
        /// enter if this state wasn't called
        /// </summary>
        /// <param name='baseState'>
        /// The state to use if there is no waiting calling state
        /// </param>
        public void Return(AiAction baseState)
        {
            
            if (state.exitState != null)
            {
                exitStateEnumerator = state.exitState();
                exitStateCoroutine.Run(exitStateEnumerator);

            }
            if (_mIEnumStack.Count > 0)
            {
                state = StateStack.getStack().Pop();
                enterStateCoroutine.Run(state.enterState());

            }
            else
            {
                currentState = baseState;
            }
            _timeEnteredState = Time.time - state.time;

        }
    }
}
//